#include "precomp.h"
#include "World.h"

#include "Map/Grid.h"
#include "Entity/Entity.h"
#include "Entity/Modules/EntityUnitStats.h"
#include "Entity/Modules/EntityBuilding.h"
#include "Entity/Modules/EntityDoodadd.h"
#include "Entity/Modules/EntityMovement.h"
#include "Entity/Modules/EntityLogic.h"
#include "Entity/Modules/EntityProjectile.h"
#include "Map/Pathfinding.h"

namespace Tmpl {

static DataValuePoolPointers<Entity> gEntities;

#define WORLD_MAX_ENT_ARRAY 1024
static Entity** entArray;

void World::Init()
{
	entArray = MALLOC64(WORLD_MAX_ENT_ARRAY, Entity*);
	Render::Init();

	Grid::Init(1024);
	Navigator::Init();

	gEntities.Init();
	MessageSystem::Init();

	EntityLogicSystem::Init();
	EntityUnitStatSystem::Init();
	EntityMovementSystem::Init();

	EntityBuildingSystem::Init();
	EntityDoodaddSystem::Init();
	EntityProjectileSystem::Init();

	EmpireSystem::Init();
}
void World::Clear()
{
	FREE64(entArray);
	MessageSystem::Clear();

	EmpireSystem::Clear();
	EntityLogicSystem::Clear();
	EntityUnitStatSystem::Clear();
	EntityMovementSystem::Clear();

	EntityBuildingSystem::Clear();
	EntityDoodaddSystem::Clear();
	EntityProjectileSystem::Clear();

	gEntities.Clear();

	Navigator::Clear();
	Grid::Clear();
}
void World::GenerateMap(int seed, int difficulty)
{
	srand(seed);
	float fSize = float(Grid::GetSize());

	int humanEmpire = World::NewEmpire("Humans");
	for (float y=0; y<100; y+=3)
	for (float x=0; x<100; x+=3)
	{
		//World::AddEntity(humanEmpire, Vec2f(x, y), Vec2f(0,1), "Townhall", 1);
	}
	
	//World::AddEntity(humanEmpire, Vec2f(5, 5), Vec2f(0,1), "Townhall", 1);

	for (int i=0; i<50000; ++i)
	{
		World::AddEntity(humanEmpire, Vec2f(RandF(fSize), RandF(fSize)), Vec2f(0,1), "Peasant", 1);
	}

	int neutralEmpire = World::NewEmpire("Neutral");
	for (int i=0; i<250000; ++i)
	{
		World::AddEntity(neutralEmpire, Vec2f(RandF(fSize), RandF(fSize)), Vec2f(0,1), "Tree", rand() % 10);
		//World::AddEntity(neutralEmpire, Vec2f(RandF(fSize), RandF(fSize)), Vec2f(0,1), "Footman", 1);
	}



	/*
	// add empire
	for (int i=0; i<50; ++i)
	{
		//World::AddEntity(gEmpire, Vec2f(RandF(50), RandF(50)), "Footman", 1);
		World::AddEntity(gEmpire, Vec2f(RandF(50), RandF(50)), "Tree", 1);
		//World::AddEntity(gEmpire, Vec2f(10, 10), "Footman", 1);
	}
	World::AddEntity(gEmpire, Vec2f(5, 5), "HQ", 1);
	World::AddEntity(gEmpire, Vec2f(2, 5), "HQ", 1);
	World::AddEntity(gEmpire, Vec2f(5, 2), "HQ", 1);
	World::AddEntity(gEmpire, Vec2f(2, 2), "HQ", 1);
	*/
}
// Empires
int World::GetEmpireCount()
{
	return EmpireSystem::GetEmpireCount();
}
int World::NewEmpire(const char* race)
{
	return EmpireSystem::NewEmpire(race);
}
Empire* World::GetEmpire(int id)
{
	return EmpireSystem::GetEmpire(id);
}
// Entities
Entity* World::GetEntity(int arrayIdx)
{
	return gEntities.GetUnmappedValue(arrayIdx);
}
Entity* World::GetEntityById(int id)
{
	return gEntities.GetValue(id);
}
void World::AddEntity(int empireId, Vec2f pos, Vec2f facing, const char* unitName, int level)
{
	int entId = gEntities.NewValue();
	Entity* ent = gEntities.GetValue(entId);
	Empire* empire = EmpireSystem::GetEmpire(empireId);
	UnitData data = empire->GetUnitData(unitName, level);
	pos = Grid::SolveOverlap(pos, 0.5f, entArray, WORLD_MAX_ENT_ARRAY);
	ent->Init(entId, -1, empireId, pos, facing, data);
}
void World::AddEntity(int empireId, int ownerId, Vec2f pos, Vec2f facing, const UnitData& data)
{
	int entId = gEntities.NewValue();
	Entity* ent = gEntities.GetValue(entId);
	ent->Init(entId, ownerId, empireId, pos, facing, data);
}

int World::GetEntityCount()
{
	return gEntities.GetValueCount();
}

void World::HandleEntityMessages()
{
	int count = gEntities.GetValueCount();
	for (int i=0; i<count; ++i)
	{
		Entity* ent = gEntities.GetUnmappedValue(i);
		if (ent->IsAlive()) ent->HandleAllMessages();
	}
}

// Update
static void CleanRemovedEntities()
{
	for (int i=gEntities.GetValueCount()-1; i>=0; --i)
	{
		Entity* ent = gEntities.GetUnmappedValue(i);
		if (ent->AllowRemove())
		{
			int entId = ent->GetId();
			ent->Remove();
			gEntities.RemoveValue(entId);
		}
		else if (ent->IsRemoved())
		{
			ent->IncDeathTimer();
		}
	}
}

JobHandle World::Update(JobHandle dependency)
{
	// Update all modules
	JobHandle building		= EntityBuildingSystem::Update(dependency);
	JobHandle doodadd		= EntityDoodaddSystem::Update(building);
	JobHandle projectile	= EntityProjectileSystem::Update(doodadd);
	JobHandle logic			= EntityLogicSystem::Update(projectile);
	JobHandle unitStats		= EntityUnitStatSystem::Update(logic);
	JobHandle movement		= EntityMovementSystem::Update(unitStats);
	JobHandle empire		= EmpireSystem::Update(movement);

	// cleanup entities
	JobHandle cleanup = JobManager::NewJob(&CleanRemovedEntities);
	JobManager::AddJobDependency(cleanup, empire);
	JobManager::AddExecuteJob(cleanup);

	// return last depency
	return cleanup;
}


// Render
void World::Render(float dt, const Vec2f& camPos, float zoom)
{
	int mapPosX = int(camPos.x);
	int mapPosY = int(camPos.y);

	int cellsX = int(float(WINDOW_W) / zoom) + 1;
	int cellsY = int(float(WINDOW_H) / zoom) + 1;

	int minX = Clamp(0, Grid::GetSize()-1, mapPosX-1);
	int maxX = Clamp(0, Grid::GetSize()-1, mapPosX + cellsX + 1);
	int minY = Clamp(0, Grid::GetSize()-1, mapPosY-1);
	int maxY = Clamp(0, Grid::GetSize()-1, mapPosY + cellsY + 1);

	// Render the map
	for (int y=minY; y<maxY; ++y)
	for (int x=minX; x<maxX; ++x)
	{
		Vec2f pos = Vec2f(float(x), float(y));
		Vec3f color;
		int col = Grid::GetCollisionIdFromXY(x, y);
		switch (col)
		{
		case Grid::COLLISION_WATER: color = Vec3f(0,0,0.25f); break;
		case Grid::COLLISION_NONE: color = Vec3f(0,0.25f,0); break;
		default: color = Vec3f(0.25f,0,0); break;
		};
		Render::RenderQuadOutlined(pos, pos+Vec2f(1,1), color, 1.0f, Vec3f(0.2f,0.5f,0.2f));
	}

	// Draw the plans
	glBegin(GL_LINES);
	for (int y=minY; y<maxY; ++y)
	for (int x=minX; x<maxX; ++x)
	{
		const GridCell& cell = Grid::GetEntitiesInCell(x,y);
		for (GridEntArray::const_iterator it=cell.begin(); it != cell.end(); ++it)
		{
			const Entity* ent = *it;
			if (ent->IsAlive() && ent->IsUnit())
			{
				ent->RenderLogic();
			}
		}
	}
	glEnd(); 

	glBegin(GL_LINES);
	for (int y=minY; y<maxY; ++y)
	for (int x=minX; x<maxX; ++x)
	{
		const GridCell& cell = Grid::GetEntitiesInCell(x,y);
		for (GridEntArray::const_iterator it=cell.begin(); it != cell.end(); ++it)
		{
			const Entity* ent = *it;
			Vec2f pos = ent->GetPos();
			Vec2f facing = ent->GetFacing();
			float size = ent->GetSize();
			int type = 0;
			if (ent->IsHero()) type = Entity::ENTITY_HERO;
			else if (ent->IsHero()) type = Entity::ENTITY_HERO;
			else if (ent->IsUnit()) type = Entity::ENTITY_UNIT;
			else if (ent->IsTree()) type = Entity::ENTITY_TREE;
			else if (ent->IsDoodadd()) type = Entity::ENTITY_DOODADD;
			else if (ent->IsProjectile()) type = Entity::ENTITY_PROJECTILE;
			Render::RenderModel(type, pos, facing, size);
		}
	}
	glEnd(); 

	/*
	// Render healthbars
	glBegin(GL_QUADS);
	for (int i=0; i<gEntities.GetValueCount(); ++i)
	{
		Entity* ent = gEntities.GetUnmappedValue(i);
		if (ent->IsAlive())
		{
			Vec2f pos = ent->GetPos();
			float size = ent->GetSize();
			Render::RenderHealthbar(pos, size, ent->GetHP() / ent->GetHPMax());
		}
	}
	*/
}
}